
#ifndef __gui_layoutH__
#define __gui_layoutH__

#include "gui_widgets.h"

namespace WtfGui {
	template <class tWidgetSize = InterpolatedWidgetVector, class tWidgetPosition = WidgetVector>
	class WidgetLayoutStackItem: public WidgetLayout<tWidgetSize, tWidgetPosition> {
		IWidgetLayout::tRef mrPreviousLayout;
		iVector2D			mvPreviousSize, mvPreviousPosition;

		public:
			explicit WidgetLayoutStackItem(const IWidgetLayout::tRef& rPrevious)
			: WidgetLayout<tWidgetSize, tWidgetPosition>(rPrevious), mrPreviousLayout(rPrevious),
			  mvPreviousSize(rPrevious->getSize()), mvPreviousPosition(rPrevious->getPosition()) {
			};

			virtual IWidgetLayout::tRef getPreviousLayout() const {
				// Restore original size & position
				mrPreviousLayout->setSize(mvPreviousSize);
				mrPreviousLayout->setPosition(mvPreviousPosition);
				return mrPreviousLayout;
			};

			// Changes bubble up to previous layout, so restore is seamless
			virtual void setSize(const iVector2D& v) {
				WidgetLayout<tWidgetSize, tWidgetPosition>::setSize(v);
				mrPreviousLayout->setSize(v);
			};
			virtual void setPosition(const iVector2D& v) {
				WidgetLayout<tWidgetSize, tWidgetPosition>::setPosition(v);
				mrPreviousLayout->setPosition(v);
			};
	};

	
	/**
	 * Allows the layout of a widget to be based upon the combination of many
	 * rules, allowing automatic sizing and positioning. This class augments
	 * its base layout, and is automatically destroyed when its base layout is
	 * destroyed.
	 **/
	class AdvancedLayout: public WtfEngine::GcObject {
		GC_AUTOSIZE(AdvancedLayout);

	public:
		class LayoutRule : public WtfEngine::GcObject {
			GC_AUTOSIZE(LayoutRule);

		protected:
			tRef mrNextRule;

		public:
			inline LayoutRule(const tRef& rNextRule = tRef()): WtfEngine::GcObject(), mrNextRule(rNextRule) {};

			virtual int getValue(int iParentVal, int iAxis) const {
				if(mrNextRule.isNull()) {
					return iParentVal;
				} else {
					return mrNextRule->getValue(iParentVal, iAxis);
				};
			};

			virtual void Init(const AdvancedLayout::tRef& rLayout) {
				if(!mrNextRule.isNull()) {
					mrNextRule->Init(rLayout);
				};
			};
			virtual void Destroy(const AdvancedLayout::tRef& rLayout) {
				if(!mrNextRule.isNull()) {
					mrNextRule->Destroy(rLayout);
				};
			};

			inline tRef Then(const tRef& rNextRule) {
				if(mrNextRule.isNull()) {
					mrNextRule = rNextRule;
				} else {
					mrNextRule->Then(rNextRule);
				};
				return this;
			};
		};

	protected:
		IWidgetLayout::tWeakRef mrBaseLayout;
		LayoutRule::tRef mvrPositionLayout[2];
		LayoutRule::tRef mvrSizeLayout[2];

	public:
		AdvancedLayout(const IWidgetLayout::tRef& rBaseLayout, const LayoutRule::tRef& rPosX, const LayoutRule::tRef& rPosY,
			const LayoutRule::tRef& rSizeX, const LayoutRule::tRef& rSizeY);

		void Update();
		void Destroy();

		//// Layout Rules

		class Displacement : public LayoutRule {
			int miDisplacement;

		public:
			inline Displacement(int iAmt, const LayoutRule::tRef& rNextRule = LayoutRule::tRef())
				: LayoutRule(rNextRule), miDisplacement(iAmt) {
			};

			virtual int getValue(int iParentVal, int iAxis) const {
				return LayoutRule::getValue(iParentVal + miDisplacement, iAxis);
			};

			GC_AUTOSIZE(Displacement);
		};
		class Scale : public LayoutRule {
			float mfScale;

		public:
			inline Scale(float fAmt, const LayoutRule::tRef& rNextRule = LayoutRule::tRef())
				: LayoutRule(rNextRule), mfScale(fAmt) {
			};

			virtual int getValue(int iParentVal, int iAxis) const {
				return LayoutRule::getValue((int)((float)iParentVal * mfScale), iAxis);
			};

			GC_AUTOSIZE(Scale);
		};


		class LayoutDecorator: public LayoutRule {
		protected:
			IWidgetLayout::tRef mrParentLayout;

			inline LayoutDecorator(const IWidgetLayout::tRef& rParentLayout, const LayoutRule::tRef& rNextRule)
				: LayoutRule(rNextRule), mrParentLayout(rParentLayout) {
			};

		public:
			virtual int getLayoutValue(int iParentValue, int iAxis) const = 0;
			virtual int getValue(int iParentVal, int iAxis) const {
				return LayoutRule::getValue(getLayoutValue(iParentVal, iAxis), iAxis);
			};

			virtual void Init(const AdvancedLayout::tRef& rLayout);
			virtual void Destroy(const AdvancedLayout::tRef& rLayout);

			GC_AUTOSIZE(LayoutDecorator);
		};

		/**
		 * Gets/sets the size of the parent layout in the specified axis.
		 **/
		class SizeOf: public LayoutDecorator {
		public:
			inline SizeOf(const IWidgetLayout::tRef& rParentLayout, const LayoutRule::tRef& rNextRule = LayoutRule::tRef())
				: LayoutDecorator(rParentLayout, rNextRule) {
			};

			virtual int getLayoutValue(int iParentVal, int iAxis) const {
				return mrParentLayout->getSize()[iAxis];
			};

			GC_AUTOSIZE(SizeOf);
		};
		class InnerSizeOf: public LayoutDecorator {
		public:
			inline InnerSizeOf(const IWidgetLayout::tRef& rParentLayout, const LayoutRule::tRef& rNextRule = LayoutRule::tRef())
				: LayoutDecorator(rParentLayout, rNextRule) {
			};

			virtual int getLayoutValue(int iParentVal, int iAxis) const {
				return mrParentLayout->getInnerSize()[iAxis];
			};

			GC_AUTOSIZE(InnerSizeOf);
		};

		/**
		 * Gets/sets the position of the parent layout in the specified axis.
		 **/
		class PositionOf: public LayoutDecorator {
		public:
			inline PositionOf(const IWidgetLayout::tRef& rParentLayout, const LayoutRule::tRef& rNextRule = LayoutRule::tRef())
				: LayoutDecorator(rParentLayout, rNextRule) {
			};

			virtual int getLayoutValue(int iParentVal, int iAxis) const {
				return mrParentLayout->getPosition()[iAxis];
			};

			GC_AUTOSIZE(PositionOf);
		};


		/**
		 * Subtracts the value given by the specified auxiliary layout rule.
		 **/
		class Subtract: public LayoutRule {
			LayoutRule::tRef mrAuxRule;

		public:
			inline Subtract(const LayoutRule::tRef& rAuxRule, const LayoutRule::tRef& rNextRule = LayoutRule::tRef())
				: LayoutRule(rNextRule), mrAuxRule(rAuxRule) {
			};

			virtual int getValue(int iParentVal, int iAxis) const {
				iParentVal -= mrAuxRule->getValue(0, iAxis);
				return LayoutRule::getValue(iParentVal, iAxis);
			};

			GC_AUTOSIZE(Subtract);
		};
		/**
		 * Adds the value given by the specified auxiliary layout rule.
		 **/
		class Add: public LayoutRule {
			LayoutRule::tRef mrAuxRule;

		public:
			inline Add(const LayoutRule::tRef& rAuxRule, const LayoutRule::tRef& rNextRule = LayoutRule::tRef())
				: LayoutRule(rNextRule), mrAuxRule(rAuxRule) {
			};

			virtual int getValue(int iParentVal, int iAxis) const {
				iParentVal += mrAuxRule->getValue(0, iAxis);
				return LayoutRule::getValue(iParentVal, iAxis);
			};

			GC_AUTOSIZE(Add);
		};


		// Helper functions
		inline static LayoutRule::tRef Default() {
			return new LayoutRule();
		};
		inline static LayoutRule::tRef PositionAfter(const IWidgetLayout::tRef& rLayout) {
			return new PositionOf(rLayout, new Add(new SizeOf(rLayout)));
		};
		inline static LayoutRule::tRef SizeAdd(const IWidgetLayout::tRef& rLayout, int iAmt) {
			return new SizeOf(rLayout, new Displacement(iAmt));
		};
		inline static LayoutRule::tRef InnerSizeAdd(const IWidgetLayout::tRef& rLayout, int iAmt) {
			return new InnerSizeOf(rLayout, new Displacement(iAmt));
		};
		inline static LayoutRule::tRef InnerSizeScale(const IWidgetLayout::tRef& rLayout, float fAmt) {
			return new InnerSizeOf(rLayout, new Scale(fAmt));
		};
	};


	/**
	 * A container which automatically positions its child elements along
	 * a specified axis.
	 **/
	class LayoutContainer: public WidgetContainer {
		protected:
			int	miElementSpacing;
			int miMinSize, miMaxSize;
			int miAxis;
			bool mbWrap;

		public:
			typedef struct LayoutContainerCreationOptions {
				/// Padding between child elements
				int	iElementSpacing;
				int iMinSize, iMaxSize;
				bool bWrap;

				LayoutContainerCreationOptions();
			} tCreationOptions;

			static const int HORIZONTAL = 0;
			static const int VERTICAL = 1;

			/// Constructor
			LayoutContainer(const tCreationOptions& opts, int axis);

			/// Positions child elements
			void UpdateLayout();

			virtual void AddChild(const AbstractWidget::tRef& rChild);
			virtual void RemoveChild(const AbstractWidget::tRef& rChild);
			virtual void Clear();

			virtual void OnResize();
			virtual void OnChildResize();

			GC_OBJECT(LayoutContainer, GC_ADDEDSIZE_AUTO(WidgetContainer, LayoutContainer));
	};

	
	/**
	 * Displays a scrollbar allowing child widgets to be scrolled
	 * on overflow.
	 **/
	class ScrollLayout : public LayoutContainer {
		int miWheelScrollStep;
		int miAmtScrolled;

	public:
		typedef struct ScrollLayoutCreationOptions : public LayoutContainer::tCreationOptions {
			int WheelScrollStep;
			ScrollLayoutCreationOptions();
		} tCreationOptions;

		ScrollLayout(const tCreationOptions& opts, int axis);

		virtual void Paint(float depthChange);
		virtual bool OnMouseUp(const iVector2D& vPos, WtfEngine::tMouseButton btn);

		GC_OBJECT(ScrollLayout, GC_ADDEDSIZE_AUTO(LayoutContainer, ScrollLayout));
	};


	/**
	 * Wraps and positions a widget based upon a rule for each axis.
	 **/
	class LayoutPositioner : public WidgetWrapper {
		public:
			class IPositioner : public virtual IGcObject {
				public:
					virtual void PositionWidget(AbstractWidget* rWidget, const WidgetContainer::tRef& rParent, int axis) = 0;

					GC_INTERFACE(IPositioner);
			};

			class Default : public IPositioner, public GcObject {
				int miOffset;

			public:
				Default(int iOffset = 0): miOffset(iOffset) {};

				void PositionWidget(AbstractWidget* rWidget, const WidgetContainer::tRef& rParent, int axis);
			};
			class Inverted : public IPositioner, public GcObject {
				int miOffset;

			public:
				Inverted(int iOffset = 0): miOffset(iOffset) {};

				void PositionWidget(AbstractWidget* rWidget, const WidgetContainer::tRef& rParent, int axis);
			};

			class Top: public Default {
				public:
					Top(int iOffset = 0): Default(0) {};
			};
			class Left: public Default {
				public:
					Left(int iOffset = 0): Default(0) {};
			};
			class Bottom: public Inverted {
				public:
					Bottom(int iOffset = 0): Inverted(0) {};
			};
			class Right: public Inverted {
				public:
					Right(int iOffset = 0): Inverted(0) {};
			};

			class Centre : public IPositioner, public GcObject {
				int miOffset;

			public:
				Centre(int iOffset = 0): miOffset(iOffset) {};

				void PositionWidget(AbstractWidget* rWidget, const WidgetContainer::tRef& rParent, int axis);
			};

		private:
			VectorType<IPositioner::tRef, 2>::tType	mvrPositioner;

		public:
			LayoutPositioner(AbstractWidget *pWidget, const IPositioner::tRef& rXPos, const IPositioner::tRef& rYPos);

			void Update();

			void OnInsert(WidgetContainer* pParent);
			void OnResize();
			void OnParentResize();
	};

	class LayoutHider: public LayoutContainer {
		AbstractWidget::tRef mrHider;

		class HiderAction: public WidgetWrapper {
			LayoutHider * mpHider;

		public:
			HiderAction(AbstractWidget* pWidget, LayoutHider* pHider);

			bool OnMouseDown(const iVector2D& vPos, WtfEngine::tMouseButton btn);
		};

	public:
		typedef struct LayoutHiderCreationOptions: public LayoutContainer::tCreationOptions {
			LayoutHiderCreationOptions();
		} tCreationOptions;

		LayoutHider(const tCreationOptions& opts, int axis, AbstractWidget::tRefa rHider);

		bool Toggle();
		void Hide();
		void Show();

		virtual void Clear();
	};
};

#endif
